"""
The database model for the Worde blogging system
"""
__revision__ = "$Id: model.py 47 2007-03-12 13:57:13Z jens.persson $"
from datetime import datetime
from turbogears.database import PackageHub
from sqlobject import SQLObject, RelatedJoin, SQLObjectNotFound
from sqlobject import UnicodeCol, DateTimeCol, StringCol, IntCol #IGNORE:E0611
from sqlobject import BoolCol, EnumCol, ForeignKey, MultipleJoin, AND #IGNORE:E0611
from turbogears import identity, widgets, validators
from worde import plugins

hub = PackageHub("worde") #IGNORE:C0103
__connection__ = hub

class Post(SQLObject):
    """A Post in the system"""
    title = UnicodeCol(length=255)
    postkey = StringCol(length=255,
                        default=None)

    permalink = UnicodeCol(length=255, default=None)
    def _set_permalink(self,v):
        pass
    def _get_permalink(self):
        if self._SO_get_permalink() == None:
            key = ""
            for c in self.title.lower():
                if c.isalnum():
                    key += c
                elif c.isspace():
                    key += "-"
            #TODO: make permalinkstructure setable
            permalink = self.created.strftime("/posts/%Y/%m/%d/")+key
            self._SO_set_permalink(permalink)
        return self._SO_get_permalink()


    author = UnicodeCol(length=255)
    tags = UnicodeCol(length=255)
    created = DateTimeCol(default=datetime.now)
    text = UnicodeCol()
    prerenderedtext = UnicodeCol(default=None)

    def _set_text(self, text):
        self._SO_set_text(text)
        text = plugins.prerender(text)
        self._SO_set_prerenderedtext(text)
    def _set_prerenderedtext(self, text):
        if text != None:
            self._set_text(text)
    def _set_unrenderedtext(self, text):
        if text != None:
            self._set_text(text)

    def _get_text(self):
        return self.prerenderedtext
    def _get_unrenderedtext(self):
        return self._SO_get_text()

    comments = MultipleJoin('Comment')
    published = BoolCol()


class Comment(SQLObject):
    """A comment to a post"""
    title = UnicodeCol(length = 32)
    author = UnicodeCol(length = 32)
    authorlink = UnicodeCol(length = 32, default="")
    created = DateTimeCol(default=datetime.now)
    post = ForeignKey('Post')

    text = UnicodeCol()
    prerenderedtext = UnicodeCol(default=None)

    def _set_text(self, text):
        self._SO_set_text(text)
        text = plugins.prerender_comment(text)
        self._SO_set_prerenderedtext(text)
    def _set_prerenderedtext(self, text):
        if text != None:
            self._set_text(text)
    def _set_unrenderedtext(self, text):
        if text != None:
            self._set_text(text)

    def _get_text(self):
        return self.prerenderedtext
    def _get_unrenderedtext(self):
        return self._SO_get_text()

    status = EnumCol(enumValues=('published', 'moderated', 'spam', 'new'), default='new', notNone=True)


class Configuration(SQLObject):
    """A configuration option"""
    name = UnicodeCol(length = 32, alternateID=True,
                      alternateMethodName="by_name")

    type = StringCol(length = 16)
    types = ["string", "url", "integer", "float"]
    def _set_type(self, value):
        if not value in self.types:
            raise ValueError("Type must be one of: " +
                             " ".join(self.types))
        self._SO_set_type(value)

    value = UnicodeCol(length = 255, default=None)
    def _get_value(self):
        """get the value of this configuration option"""
        if self.type == "integer":
            return int(self._SO_get_value())
        elif self.type == "string" or self.type == "url":
            return self._SO_get_value()
        elif self.type == "float":
            return float(self._SO_get_value())
    def _set_value(self, value):
        """set the value of this configuration option"""
        if self.type == "integer" or self.type == "float":
            self._SO_set_value(str(value))
        elif self.type == "string" or self.type == "url":
            self._SO_set_value(value)

    def widget(self, **kw):
        """
        A widget sutable to configure the value
        """
        if self.type == "string":
            return widgets.TextField(validator=validators.NotEmpty(),
                                     default = self.value,
                                     attrs = {'size':30}, **kw)
        elif self.type == "url":
            return widgets.TextField(validator=validators.URL(),
                                     default = self.value,
                                     attrs = {'size':30}, **kw)
        elif self.type == "float":
            return widgets.TextField(validator=validators.Number(ifEmpty=0),
                                     default = self.value,
                                     attrs = {'size':30}, **kw)
        elif self.type == "integer":
            return widgets.TextField(validator=validators.Int(ifEmpty=0),
                                     default = self.value,
                                     attrs = {'size':30}, **kw)
        else:
            raise ValueError("Type cant be: " + self.type)

class PluginConfiguration(SQLObject):
    """
    A plugins configuration
    """
    name = UnicodeCol(length = 32)
    module = UnicodeCol(length = 32)
    hook = UnicodeCol(length = 32)
    active = BoolCol(default = False)
    ordering = IntCol(default = 0)

    @classmethod
    def get_singelton(cls, module, name, hook):
        """
        Return a instance of the configuration value, creating one if needed
        """
        pcs = list(cls.selectBy(module = module, name = name, hook = hook))
        if pcs:
            return pcs[0]
        else:
            return cls(module = module, name = name, hook = hook)

    def _get_uniquename(self):
        return "%s_%s_%s" % (self.module, self.name, self.hook)

    @classmethod
    def normalize_ordering(cls, hook=None):
        """
        Normalize the ordering columns to be [1,2,3,...]
        If no hook is given, normalize all
        """
        if hook:
            hooks = [hook]
        else:
            import worde.plugins
            hooks = worde.plugins.API

        for h in hooks:
            plugs = list(cls.selectBy(hook = h))
            plugs.sort(lambda a,b:cmp(a.ordering,b.ordering))
            val = 1
            for p in plugs:
                p.ordering = val
                hub.commit()
                val += 1


class Visit(SQLObject):
    """
    A visit, ie. a collection of page fetches with the same key-cookie
    """
    class sqlmeta:
        table = "visit"

    visit_key = StringCol(length = 40, alternateID=True,
                          alternateMethodName="by_visit_key")
    created = DateTimeCol(default=datetime.now)
    expiry = DateTimeCol()

    def lookup_visit(cls, visit_key):
        """
        Fetch a visit by its key
        """
        try:
            return cls.by_visit_key(visit_key)
        except SQLObjectNotFound:
            return None
    lookup_visit = classmethod(lookup_visit)

class VisitIdentity(SQLObject):
    "The user of a perticular visit"
    visit_key = StringCol(length=40, alternateID=True,
                          alternateMethodName="by_visit_key")
    user_id = IntCol()


class Group(SQLObject):
    """
    An ultra-simple group definition.
    """

    # names like "Group", "Order" and "User" are reserved words in SQL
    # so we set the name to something safe for SQL
    class sqlmeta:
        table = "tg_group"

    group_name = UnicodeCol(length=16, alternateID=True,
                            alternateMethodName="by_group_name")
    display_name = UnicodeCol(length=255)
    created = DateTimeCol(default=datetime.now)

    # collection of all users belonging to this group
    users = RelatedJoin("User", intermediateTable="user_group",
                        joinColumn="group_id", otherColumn="user_id")

    # collection of all permissions for this group
    permissions = RelatedJoin("Permission", joinColumn="group_id",
                              intermediateTable="group_permission",
                              otherColumn="permission_id")


class User(SQLObject):
    """
    Reasonably basic User definition. Probably would want additional attributes.
    """
    # names like "Group", "Order" and "User" are reserved words in SQL
    # so we set the name to something safe for SQL
    class sqlmeta:
        table = "tg_user"

    user_name = UnicodeCol(length=16, alternateID=True,
                           alternateMethodName="by_user_name")
    email_address = UnicodeCol(length=255, alternateID=True,
                               alternateMethodName="by_email_address")
    display_name = UnicodeCol(length=255)
    password = UnicodeCol(length=40)
    created = DateTimeCol(default=datetime.now)

    # groups this user belongs to
    groups = RelatedJoin("Group", intermediateTable="user_group",
                         joinColumn="user_id", otherColumn="group_id")

    def _get_permissions(self):
        perms = set()
        for g in self.groups:
            perms = perms | set(g.permissions)
        return perms

    def _set_password(self, cleartext_password):
        "Runs cleartext_password through the hash algorithm before saving."
        password_hash = identity.encrypt_password(cleartext_password)
        self._SO_set_password(password_hash)

    def set_password_raw(self, password):
        "Saves the password as-is to the database."
        self._SO_set_password(password)



class Permission(SQLObject):
    """A permission in the security system"""
    permission_name = UnicodeCol(length=16, alternateID=True,
                                 alternateMethodName="by_permission_name")
    description = UnicodeCol(length=255)

    groups = RelatedJoin("Group", intermediateTable="group_permission",
                         joinColumn="permission_id", otherColumn="group_id")
